-- ex. 1
safeHead :: [a] -> Maybe a
safeTail :: [a] -> Maybe [a]
safeLast :: [a] -> Maybe a
safeInit :: [a] -> Maybe [a]

ifNullNothingElse f [] = Nothing
ifNullNothingElse f x = Just (f x)

safeHead = ifNullNothingElse head
safeTail = ifNullNothingElse tail
safeLast = ifNullNothingElse last
safeInit = ifNullNothingElse init

-- ex. 2
splitWith :: (a -> Bool) -> [a] -> [[a]]
splitWith isBreak xs = outside xs
    where outside [] = []
          outside (x:xs) | isBreak x = outside xs
                         | otherwise = let (run, runs) = inside xs
                                       in (x:run) : runs
          inside [] = ([],[])
          inside (x:xs) | isBreak x = ([], outside xs)
                        | otherwise = let (run, runs) = inside xs
                                      in (x:run, runs)

-- This time, use the Prelude functions.
-- The real challenge here is avoiding redundant application of isBreak.
-- A non-empty list returned by dropWhile or break has a head whose
-- isBreak-ness is known; outside and inside must use that knowledge.
-- But that means we need to enter the recursion in the first place
-- with a known head, thus the ugly equations for for splitWith2.
splitWith2 :: (a -> Bool) -> [a] -> [[a]]
splitWith2 isBreak [] = []
splitWith2 isBreak l@(x:_) = (if isBreak x then outside else inside) l
    where outside [] = []
          outside (x:xs) = inside (dropWhile isBreak xs)
          inside [] = []
          inside (x:xs) = let (run, tail) = break isBreak xs
                          in (x:run) : outside tail

-- This solution has a single loop, which is always accumulating a
-- run. It's kind of dumb to be testing whether 'run' is null all the
-- time when the iteration that handed it to us already knew whether
-- it was null or not.
splitWith3 :: (a -> Bool) -> [a] -> [[a]]
splitWith3 isBreak xs = case scan xs of
                          ([],  runs) -> runs
                          (run, runs) -> run:runs
    where scan [] = ([],[])
          scan (x:xs) | isBreak x = ([], (if null run then runs else run:runs))
                      | otherwise = (x:run, runs)
                      where (run,runs) = scan xs
